﻿using System;
using System.Collections;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using Microsoft.Practices.Prism.Commands;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Okazuki.MVVM.PrismSupport.Interactivity;

namespace Okazuki.MVVM.PrismSupport.Test.Interactivity
{
    [TestClass]
    public class ExtendedInvokeCommandActionTest
    {
        #region Fields

        private ExtendedInvokeCommandAction targetAction;
        private UIElement eventSouceElement;
        private System.Windows.Interactivity.EventTrigger trigger;

        #endregion

        #region WithoutValueConverterTest

        /// <summary>
        /// CommandParameterに何も指定しない時に正常にCommandが呼ばれることを確認
        /// </summary>
        [TestMethod]
        public void ExtendedInvokeCommandAction_WithoutValueConverter_CommandParameterTest_Null()
        {
            InitializeItems<Button>("Click");

            var actualCalled = false;

            targetAction.Command = new DelegateCommand(
                () =>
                {
                    actualCalled = true;
                }
            );

            eventSouceElement.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));

            Assert.IsTrue(actualCalled, "Commandが呼ばれていません。");
        }

        /// <summary>
        /// CommandParameterにIValueConverter以外を指定した時に
        /// ・正常にCommandが呼ばれること
        /// ・Commandに渡されたCommandParameterが指定したものと同じであること
        /// を確認
        /// </summary>
        [TestMethod]
        public void ExtendedInvokeCommandAction_WithoutValueConverter_CommandParameterTest_String()
        {
            InitializeItems<Button>("Click");

            var actualCalled = false;
            var actualParamter = "";

            targetAction.CommandParameter = Expectation.StringParameter;
            targetAction.Command = new DelegateCommand<string>(
                s =>
                {
                    actualCalled = true;
                    actualParamter = s;
                }
            );

            eventSouceElement.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));

            Assert.IsTrue(actualCalled, "Commandが呼ばれていません。");
            Assert.AreEqual(Expectation.StringParameter, actualParamter, "CommandParameterが一致しません");
        }

        /// <summary>
        /// CommandParameterにIValueConverter以外を指定した時に
        /// ・AssociatedObjectのIsEnabledプロパティが正常に反映されること
        /// を確認
        /// </summary>
        [TestMethod]
        public void ExtendedInvokeCommandAction_WithoutIValueConverter_EnabledTest()
        {
            InitializeItems<Button>("Click");

            var cmd = new DelegateCommand<string>(
                p =>
                {
                },
                p =>
                {
                    return Expectation.StringParameter.Equals(p);
                }
            );

            targetAction.Command = cmd;

            cmd.RaiseCanExecuteChanged();
            Assert.IsFalse(eventSouceElement.IsEnabled, "IsEnabledが反映されていません。");

            targetAction.CommandParameter = Expectation.StringParameter;
            cmd.RaiseCanExecuteChanged();
            Assert.IsTrue(eventSouceElement.IsEnabled, "IsEnabledが反映されていません。");

            targetAction.CommandParameter = "dummy";
            cmd.RaiseCanExecuteChanged();
            Assert.IsFalse(eventSouceElement.IsEnabled, "IsEnabledが反映されていません。");
        }

        #endregion

        #region WithValueConverterTest

        /// <summary>
        /// CommandParameterにIValueConverterを指定した時に
        /// ・正常にCommandが呼ばれること
        /// ・Commandに渡されたCommandParameterがIValueConverterの実装通りにEventArgsから変換されたものであること
        /// を確認
        /// </summary>
        [TestMethod]
        public void ExtendedInvokeCommandAction_WithValueConverter_CommandParameterTest()
        {
            InitializeItems<ListBox>("SelectionChanged");

            var actualCalled = false;
            SelectionChangedCommandParameter actualParameter = null;

            targetAction.Command = new DelegateCommand<SelectionChangedCommandParameter>(
                p =>
                {
                    actualCalled = true;
                    actualParameter = p;
                }
            );

            //Converterを指定
            targetAction.CommandParameter = new SelectionChangedEventArgsToCommandParameterConverter();

            eventSouceElement.RaiseEvent(new SelectionChangedEventArgs(
                Selector.SelectionChangedEvent,
                Expectation.SelectionChangedCommandParameter.RemovedItems,
                Expectation.SelectionChangedCommandParameter.AddedItems
            ));

            Assert.IsTrue(actualCalled, "Commandが呼ばれていません。");
            Assert.IsTrue(
                Expectation.SelectionChangedCommandParameter.RemovedItems.Cast<string>().SequenceEqual(actualParameter.RemovedItems.Cast<string>()),
                "RemovedItemsが一致しません。");

            Assert.IsTrue(
                Expectation.SelectionChangedCommandParameter.AddedItems.Cast<string>().SequenceEqual(actualParameter.AddedItems.Cast<string>()),
                "AddedItemsが一致しません。");
        }

        /// <summary>
        /// CommandParameterにIValueConverterを指定した時に
        /// ・AssociatedObjectのIsEnabledプロパティが反映されないこと
        /// を確認
        /// </summary>
        [TestMethod]
        public void ExtendedInvokeCommandAction_WithValueConverter_EnabledTest()
        {
            InitializeItems<ListBox>("SelectionChanged");

            var canExecute = true;

            var cmd = new DelegateCommand<string>(
                p =>
                {
                },
                P =>
                {
                    return canExecute;
                }
            );

            targetAction.Command = cmd;
            targetAction.CommandParameter = new SelectionChangedEventArgsToCommandParameterConverter();
            Assert.IsTrue(eventSouceElement.IsEnabled, "IsEnabledが変更されています。");

            cmd.RaiseCanExecuteChanged();
            Assert.IsTrue(eventSouceElement.IsEnabled, "IsEnabledが変更されています。");

            canExecute = false;
            cmd.RaiseCanExecuteChanged();
            Assert.IsTrue(eventSouceElement.IsEnabled, "IsEnabledが変更されています。");
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// テストで使用する共通フィールドを初期化します。
        /// </summary>
        /// <typeparam name="TEventSource">イベントを発生させるUIElement</typeparam>
        /// <param name="routedEventName">イベント名</param>
        private void InitializeItems<TEventSource>(string routedEventName) where TEventSource : UIElement, new()
        {
            targetAction = new ExtendedInvokeCommandAction();
            eventSouceElement = new TEventSource();
            trigger = new System.Windows.Interactivity.EventTrigger(routedEventName);
            trigger.Actions.Add(targetAction);
            trigger.Attach(eventSouceElement);
            trigger.SourceObject = eventSouceElement;
        }

        #endregion

        #region Privete Classes

        /// <summary>
        /// 期待値
        /// </summary>
        private static class Expectation
        {
            public const string StringParameter = "default parameter";

            public static readonly SelectionChangedCommandParameter SelectionChangedCommandParameter =
                new SelectionChangedCommandParameter(
                    new string[] { "r1", "r2" }, new string[] { "a1", "a2" });
        }

        /// <summary>
        /// SelectionChangedEventをCommandParameterに変換するIValueConverterの実装
        /// </summary>
        private class SelectionChangedEventArgsToCommandParameterConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                var args = (SelectionChangedEventArgs)value;
                return new SelectionChangedCommandParameter(args.RemovedItems, args.AddedItems);
            }

            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// SelectedChangedEventArgs用のCommandParameterクラス
        /// </summary>
        private class SelectionChangedCommandParameter
        {
            public SelectionChangedCommandParameter(IList removedItems, IList addedItems)
            {
                RemovedItems = removedItems;
                AddedItems = addedItems;
            }

            public IList RemovedItems { get; private set; }
            public IList AddedItems { get; private set; }
        } 

        #endregion
    }
}
